Skip to content

CFE-2986: Added getdir command to cf-net, copies directory and files#6049

Open
SimonThalvorsen wants to merge 1 commit intocfengine:masterfrom
SimonThalvorsen:CFE-2986
Open

CFE-2986: Added getdir command to cf-net, copies directory and files#6049
SimonThalvorsen wants to merge 1 commit intocfengine:masterfrom
SimonThalvorsen:CFE-2986

Conversation

@SimonThalvorsen
Copy link
Contributor

Ticket: CFE-2986
Changelog: Title

@SimonThalvorsen SimonThalvorsen requested a review from larsewi March 2, 2026 10:02
@larsewi larsewi changed the title Added getdir command to cf-net, copies directory and files CFE-2986: Added getdir command to cf-net, copies directory and files Mar 2, 2026
Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer if we extended cf-net get to get both files and directories, instead of having two separate sub-commands. Please investigate how rsync does this and try to do it in a similar way here.

@olehermanse
Copy link
Member

olehermanse commented Mar 4, 2026

It would be nicer if we extended cf-net get to get both files and directories, instead of having two separate sub-commands. Please investigate how rsync does this and try to do it in a similar way here.

@larsewi on the other hand, cf-net was written mainly as a protocol testing tool, and the commands mirror their protocol command names. If the GET protocol command in CFEngine network protocol can only GET a file, it might be a bit confusing that cf-net get is something very different.

There is also some advantage to not being too dynamic with this stuff. If you make a CLI that forces the user to specify if they are copying a file or a folder, you can prevent some annoying / confusing situations.

We could for example make a new subcommand, copy;

$ cf-net copy --file promises.cf
Copying file 1.2.3.4:/var/cfengine/masterfiles/promises.cf -> ./promises.cf
$ cf-net copy --file services/
Error: 'services/' does not look like a file, did you mean --directory?
$ cf-net copy --file services/.
Error: 'services/.' does not look like a file, did you mean --directory?
$ cf-net copy --directory services/
Copying directory 1.2.3.4:/var/cfengine/masterfiles/services/ -> ./services/
$ cf-net copy services/
Copying directory 1.2.3.4:/var/cfengine/masterfiles/services/ -> ./services/
$ cf-net copy services
Warning: Ambiguous if 'services' is a file or a directory - consider adding --file or --directory.
Copying directory 1.2.3.4:/var/cfengine/masterfiles/services/ -> ./services/

Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please break this up a little. Maybe a recursive solution would be more elegant given that it is a recursive problem.

char *local_dir = NULL;

int argc = 0;
while (args[argc] != NULL)

Check warning

Code scanning / CodeQL

Poorly documented large function Warning

Poorly documented function: fewer than 2% comments for a function of 115 lines.
@SimonThalvorsen SimonThalvorsen requested a review from larsewi March 9, 2026 14:42
Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a recursion depth limit to prevent a potential malicious or misconfigured server with symlink loops (or deeply nested dirs) to cause stack overflow.

We should probably stick to a maximum path size for the same reasons above.

@SimonThalvorsen SimonThalvorsen force-pushed the CFE-2986 branch 2 times, most recently from d68100f to 047b068 Compare March 12, 2026 14:29
@SimonThalvorsen SimonThalvorsen requested a review from larsewi March 12, 2026 14:37
Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to check return values of functions. It's OK to do best effort. I.e., we encounter an error but continue if it's safe. But in this case we need to cache the error so that we can later exit with failure. For some errors, like if we fail to create a directory, it does not make sense to try to fetch the files that should be in that directory afterwards.

@SimonThalvorsen SimonThalvorsen force-pushed the CFE-2986 branch 2 times, most recently from 62eb2df to c2b59c7 Compare March 13, 2026 15:49
Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's starting to look good. Just a few more nitpicks. Almost there!

Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's starting to look very good

}
}

args = &(args[optind]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no check that a positional argument remains. Running cf-net getdir -o /tmp/ (no remote dir) will probably dereference NULL

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are still dereferencing args[optind] here without checking that there is anything there first

Ticket: CFE-2986
Changelog: Title

Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
Copy link
Contributor

@larsewi larsewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting there! Still a few things to address before this is ready to merge

Comment on lines +1124 to +1128
ret = process_dir_recursive(conn,
remote_full,
local_full,
print_stats,
(limit - 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indents here (using 8 spaces) are a bit inconsistent with the rest of the code base

}

// Helper: Recursively process directory entries
static int process_dir_recursive(AgentConnection *conn,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A doc comment explaining the return values or a self documenting enum would be nice

}

// Helper: Sets up local directory filepath
bool static SetupLocalDirectoryPath(char *const base, char **local_dir)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment. The function name already conveys this. The comment should answer things the signature can't tell you. Otherwise it should not be there. A better comment would for example explain what the base argument does. Because this is not obvious from the signature alone. There's a few more comments like this in the code. You can consider removing or changing them.

char temp[PATH_MAX];

int written = snprintf(temp, sizeof(temp), "%s/%s/", *local_dir, base);
printf("tmp;%s\n", temp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like some left-over from debugging?

return false;
}
free(*local_dir);
*local_dir = xstrdup(temp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you could use StringFormat() instead. Or since the you are limiting the buffer to PATH_MAX you can require the caller to allocate it. This way the caller can allocate it on the stack.

Log(LOG_LEVEL_ERR, "Path too long for local path: %s/%s/",
*local_dir, base);
free(*local_dir);
free(base);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It very unexpected that this function free's something allocated by the elsewhere. The caller should be responsible.

Comment on lines +1229 to +1237
if (local_dir != NULL)
{
Log(LOG_LEVEL_INFO,
"Warning: multiple occurrences of -o in command, "\
"only last one will be used.");
free(local_dir);
}
local_dir = xstrdup(optarg);
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to just return failure here?

Comment on lines +1247 to +1249
printf("Default optarg = '%s', c = '%c' = %i\n",
optarg, c, (int)c);
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain

}
}

args = &(args[optind]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are still dereferencing args[optind] here without checking that there is anything there first

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants